package org.artifact.core.plugin.dirtyword;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.setting.Setting;
import org.artifact.core.lang.IPlugin;

import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.CharsetUtil;
import cn.hutool.log.Log;
import cn.hutool.log.LogFactory;
import org.artifact.core.lang.IServer;
import org.artifact.core.lang.MapPlus;
import org.artifact.core.plugin.storage.StoragePluginConfig;

/**
 * 屏蔽字插件
 * 
 * @author Huayco
 */
public class DirtyWordsPlugin implements IPlugin {

	static final Log log = LogFactory.get(DirtyWordsPlugin.class);

	/** 替换字星星数组 */
	private String[] replaces = null;

	/** 严格模式分隔符 */
	private NoConflictHashMapForChar splitter = new NoConflictHashMapForChar();

	private TrieFilter empty = new TrieFilter();
	private TrieFilter root = new TrieFilter();

	private DirtyWordsPluginConfig config;

	public DirtyWordsPlugin() {
		Map config = IServer.me().getContext().getConfig();
		Map storage = MapPlus.get("plugins.storage",config);
		this.config = BeanUtil.mapToBean(storage, DirtyWordsPluginConfig.class,false);
	}

	public DirtyWordsPlugin(DirtyWordsPluginConfig config) {
		this.config = config;
	}

	/**
	 * 是否包含屏蔽字
	 * 
	 * @param text
	 * @return
	 */
	public boolean contains(String text) {
		return (findOne(text) != null);
	}

	/**
	 * 检查指定字符串是否包含屏蔽字
	 * 
	 * @param text
	 *            要检查的字符串
	 * @return 返回第一个找到的屏蔽字，没有则返回null
	 */
	public String findOne(String text) {
		TrieFilter node;
		for (int i = 0; i < text.length(); i++) {
			if ((node = root.m_values.get(text.charAt(i))) != null) {
				for (int j = i + 1; j < text.length(); j++) {
					if ((node = node.m_values.get(text.charAt(j))) != null) {
						if (node.m_values.containsKey('\0')) {
							return text.substring(i, j + 1);
						}
					} else {
						break;
					}
				}
			}
		}
		return null;
	}

	/**
	 * 检查指定字符串是否包含屏蔽字，是则替换成相应长度的*号
	 * 
	 * @param text
	 *            要检查的字符串
	 * @return 返回经过过滤，不包含屏蔽字的字符串
	 */
	public String findAndReplace(String text) {
		TrieFilter node;
		TrieFilter node2;
		for (int i = 0; i < text.length(); i++) {
			if ((node = root.m_values.get(text.charAt(i))) != null) {
				int end = 0;
				if (node.m_values.containsKey('\0')) { // 碰到敏感词
					end = i + 1;
				}
				for (int j = i + 1; j < text.length(); j++) {
					if ((node2 = node.m_values.get(text.charAt(j))) != null) {
						node = node2;
						if (node.m_values.containsKey('\0')) {
							end = j + 1;
						}
					} else if (config.isStrictMode() && splitter.containsKey(text.charAt(j))) {
						if (node.m_values.containsKey('\0')) {
							end = j + 1;
						}
						continue;
					} else {
						break;
					}
				}
				if (end > 0) {
					String key = text.substring(i, end); // 截取出屏蔽字
					int l = key.length() + 1 > replaces.length ? replaces.length - 1 : key.length() - 1; // 计算替换字*号长度
					text = replace(text, key, replaces[l]); // 将屏蔽字替换成相应长度的*号
					i = end - 1 - (key.length() > replaces.length ? (key.length() - replaces.length + 1) : 0);
				}
			}
		}
		return text;
	}

	/**
	 * 高效的字符串替换
	 */
	private static String replace(String strSource, String strFrom, String strTo) {
		if (strSource == null) {
			return null;
		}
		int i = 0;
		if ((i = strSource.indexOf(strFrom, i)) >= 0) {
			char[] cSrc = strSource.toCharArray();
			char[] cTo = strTo.toCharArray();
			int len = strFrom.length();
			StringBuffer buf = new StringBuffer(cSrc.length);
			buf.append(cSrc, 0, i).append(cTo);
			i += len;
			int j = i;
			while ((i = strSource.indexOf(strFrom, i)) > 0) {
				buf.append(cSrc, j, i - j).append(cTo);
				i += len;
				j = i;
			}
			buf.append(cSrc, j, cSrc.length - j);
			return buf.toString();
		}
		return strSource;
	}

	@Override
	public boolean start() {
		// 加载严格模式分隔符
		List<String> lines = FileUtil.readLines(config.getSplitterFilePath(), CharsetUtil.UTF_8);
		for (String line : lines) {
			if (line.length() == 1) {
				if (!splitter.containsKey(line.charAt(0))) {
					splitter.put(line.charAt(0), empty);
				}
			}
		}

		// 加载屏蔽字
		lines = FileUtil.readLines(config.getDirtyWordsFilePath(), CharsetUtil.UTF_8);
		List<String> keyWords = new ArrayList<String>();
		int maxLength = 0;
		for (String line : lines) {
			if (line.length() > 0) {
				keyWords.add(line);
				if (maxLength < line.length()) {
					maxLength = line.length();
				}
			}
		}
		log.debug("屏蔽字数量：{}", keyWords.size());

		for (int i = 0; i < keyWords.size(); i++) {
			root.AddKey(keyWords.get(i),splitter);
		}
		replaces = new String[maxLength + 1];
		StringBuffer sb = new StringBuffer(40);
		for (int i = 0; i < replaces.length; i++) {
			for (int j = 0; j <= i; j++) {
				sb.append("*");
			}
			replaces[i] = sb.toString();
			sb.delete(0, sb.length());
		}
		return true;
	}

	@Override
	public boolean stop() {
		return true;
	}

}

/**
 * @author Huayco
 *         byte注：m_values使用NoConflictHashMapForChar是空间换时间的做法，使用HashMap也可以，但是查找速度比NoConflictHashMapForChar降一半。
 *         数据：13388个屏蔽字，NoConflictHashMapForChar占4061K，HashMap占2406K
 */
class TrieFilter {

	public NoConflictHashMapForChar m_values;

	// public HashMap<Character, TrieFilter> m_values;

	public TrieFilter() {
		m_values = new NoConflictHashMapForChar();
		// m_values = new HashMap<Character, TrieFilter>();
	}

	/**
	 * 添加屏蔽字
	 * 
	 * @param key
	 */
	public void AddKey(String key,NoConflictHashMapForChar splitter) {
		if (key == null || key.length() == 0) {
			return;
		}
		TrieFilter node = this; // 根节点
		for (int i = 0; i < key.length(); i++) {
			if (splitter.containsKey(key.charAt(i))) {
				continue;
			}
			TrieFilter subnode;
			if ((subnode = node.m_values.get(key.charAt(i))) == null) {
				subnode = new TrieFilter();
				node.m_values.put(key.charAt(i), subnode);
			}
			node = subnode;
		}
		// 最后的结点，表示单词结束，用‘\0’表示，指向一个空对象
		node.m_values.put('\0', null);
	}
}
